package com.bkck.sharkblockade.levels.ball;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import com.bkck.sharkblockade.fish.Fish;
import com.bkck.sharkblockade.levels.block.Block;
import com.bkck.sharkblockade.levels.explosion.Explosion;
import com.bkck.sharkblockade.levels.harpoon.Harpoon;
import com.bkck.sharkblockade.levels.item.Item;
import com.bkck.sharkblockade.levels.block.BlockEdges;
import com.bkck.sharkblockade.levels.lines.Line;
import com.bkck.sharkblockade.levels.lines.Point;
import com.bkck.sharkblockade.levels.paddle.Paddle;
import com.bkck.sharkblockade.levels.shark.Shark;
import com.bkck.sharkblockade.levels.thread.MainGamePanel;
import com.bkck.sharkblockade.levels.thread.MainThread;
import android.graphics.Bitmap;
import android.graphics.Canvas;

/**
 * The ball.
 * 
 * @author Team Bkck.
 * 
 */
public class Ball {

  // a^2 + b^2 = c^2, used to calculate the velocity of x, y so speed stays constant
  private final static int C_SQUARED = 225;

  // the speed of the ball
  private float pixelsPerMeter = 10;
  // time of last update in milliseconds
  private long lastUpdateTime = -1;
  // image bitmap
  private Bitmap bitmap;
  // dimension
  private int ballHeight;
  private int ballWidth;
  // coordinates
  private float x;
  private float y;
  // variables to solve the pass through paddle problem
  private float previousX;
  private float previousY;

  // speed in meters per second
  private float xVelocity;
  private float yVelocity;

  // whether the ball is resting on the paddle or bouncing around the screen.
  private boolean launched;
  private boolean spiky;
  private boolean ghost;
  private boolean eaten;

  // consecutive block hits before the ball hits the paddle again
  private int consecutiveHits;

  /**
   * Constructor.
   * 
   * @param bitmap The bitmap
   * @param x The x coordinate
   * @param y The y coordinate
   * @param xVelocity The horizontal velocity.
   * @param yVelocity The vertical velocity.
   */
  public Ball(Bitmap bitmap, int x, int y, int xVelocity, int yVelocity) {
    this.bitmap = bitmap;
    this.ballHeight = bitmap.getHeight();
    this.ballWidth = bitmap.getWidth();
    this.x = x;
    this.y = y;
    this.xVelocity = xVelocity;
    this.yVelocity = yVelocity;
    this.launched = false;
    this.eaten = false;
    this.consecutiveHits = 0;
  }

  /**
   * Empty constructor to be used by activities.
   */
  public Ball() {

  }

  /**
   * Whether a shark is chewing on it.
   * 
   * @return
   */
  public boolean isEaten() {
    return eaten;
  }

  public void setEaten(boolean eaten) {
    this.eaten = eaten;
  }

  /**
   * Sets whether the ball should be spiky or not.
   */
  public void setSpiky(boolean spiky) {
    this.spiky = spiky;
  }

  /** Sets whether the ball should be ghost or not. */
  public void setGhost(boolean ghost) {
    this.ghost = ghost;
  }

  /** Accessor for if the ball is ghost or not. */
  public boolean getGhost() {
    return ghost;
  }

  /**
   * Set the speed for the ball.
   * 
   * @param newPixelsPerMeter
   */
  public void setPixelsPerMeter(float pixelsPerMeter) {
    this.pixelsPerMeter = pixelsPerMeter;
  }

  /**
   * Return the speed of the ball.
   * 
   * @return
   */
  public float getPixelsPerMeter() {
    return pixelsPerMeter;
  }

  /**
   * Return the bitmap image of this ball.
   * 
   * @return The bitmap.
   */
  public Bitmap getBitmap() {
    return bitmap;
  }

  /**
   * Set the bitmap for this ball.
   * 
   * @param bitmap The bitmap.
   */
  public void setBitmap(Bitmap bitmap) {
    this.bitmap = bitmap;
    this.ballHeight = bitmap.getHeight();
    this.ballWidth = bitmap.getWidth();
  }

  /**
   * Return x coordinate for this ball.
   * 
   * @return x coordinate.
   */
  public float getX() {
    return x;
  }

  /**
   * Set x coordinate for this ball.
   * 
   * @param x coordinate
   */
  public void setX(float x) {
    this.previousX = this.x;
    this.x = x;
  }

  /**
   * Return y coordinate for this ball.
   * 
   * @return y coordinate.
   */
  public float getY() {
    return y;
  }

  /**
   * Set y coordinate for this ball.
   * 
   * @param y coordinate
   */
  public void setY(float y) {
    this.previousY = this.y;
    this.y = y;
  }

  /**
   * Return the x coordinate for this ball in the previous frame.
   * 
   * @return The previous x coordinate
   */
  public float getPreviousX() {
    return previousX;
  }

  /**
   * Return the y coordinate for this ball in the previous frame.
   * 
   * @return The previous y coordinate
   */
  public float getPreviousY() {
    return previousY;
  }

  /**
   * Return the x velocity for this ball.
   * 
   * @return The x velocity.
   */
  public float getXVelocity() {
    return xVelocity;
  }

  /**
   * Set the x velocity for this ball.
   * 
   * @return The x velocity.
   */
  public void setXVelocity(float xVelocity) {
    this.xVelocity = xVelocity;
  }

  /**
   * Return the y velocity for this ball.
   * 
   * @return The y velocity.
   */
  public float getYVelocity() {
    return yVelocity;
  }

  /**
   * Return the y velocity for this ball.
   * 
   * @return The y velocity.
   */
  public void setYVelocity(float yVelocity) {
    this.yVelocity = yVelocity;
  }

  /**
   * Return the width for this ball in pixels.
   * 
   * @return The width in pixels.
   */
  public int getWidth() {
    return ballWidth;
  }

  /**
   * Return the height for this block in pixels.
   * 
   * @return The height in pixels.
   */
  public int getHeight() {
    return ballHeight;
  }

  /**
   * Return whether the ball was launched.
   * 
   * @return launched
   */
  public boolean isLaunched() {
    return launched;
  }

  /**
   * Launch the ball.
   */
  public void launch() {
    // this.yVelocity = -10;
    adjustYVelocity();
    this.launched = true;
  }

  /**
   * Adjust yVelocity according to xVelocity.
   */
  public void adjustYVelocity() {
    yVelocity = -(float) Math.sqrt(C_SQUARED - xVelocity * xVelocity);
  }

  /**
   * Reset the ball.
   */
  public void reset() {
    this.eaten = false;
    lastUpdateTime = -1;
    consecutiveHits = 0;
    this.launched = false;
  }

  /**
   * Reset consecutive hits upon contact with the paddle or when a live was lost.
   */
  public void resetConsecutiveHits() {
    consecutiveHits = 0;
  }

  /**
   * Updates the ball position and checks for collisions.
   * 
   * @param viewHeight The height for the custom view.
   * @param viewWidth The width for the custom view.
   * @param paddle
   * @param blocks
   */
  public void update(Paddle paddle, List<Block> blocks, Item item, Harpoon harpoon,
      Explosion explosion, HashMap<Line, Block> map, BlockEdges edges) {

    // reset the ball cause the player failed to hit the ball with paddle
    // + 100 pixels to give some time before the ball resets.
    if (y >= MainGamePanel.viewHeight + 100) {
      lastUpdateTime = -1;
      MainThread.loseOneLife();
      reset();
    }

    // let the ball rest on top of the paddle prior to launching
    if (!launched && !eaten) {
      followPaddle(paddle);
      return;
    }

    long currentTime = System.currentTimeMillis();
    if (lastUpdateTime < 0) {
      lastUpdateTime = currentTime;
      return;
    }

    long elapsedTime = currentTime - lastUpdateTime;
    lastUpdateTime = currentTime;

    // bounce off of walls
    if (x <= 0 || x + ballWidth >= MainGamePanel.viewWidth || y <= 0) {

      Point intersection = null;

      // right wall
      intersection =
          findIntersection(previousX + ballWidth, previousY, x + ballWidth, y,
              MainGamePanel.viewWidth, 0, MainGamePanel.viewWidth, MainGamePanel.viewHeight);
      if (intersection != null && xVelocity > 0) {
        x = intersection.getX() - ballWidth;
        y = intersection.getY();
        xVelocity *= -1;
      }
      // left wall
      intersection =
          findIntersection(previousX, previousY, x, y, 0, 0, 0, MainGamePanel.viewHeight);
      if (intersection != null && xVelocity < 0) {
        x = intersection.getX();
        y = intersection.getY();
        xVelocity *= -1;
      }
      // top wall
      intersection = findIntersection(previousX, previousY, x, y, 0, 0, MainGamePanel.viewWidth, 0);
      if (intersection != null && yVelocity < 0) {
        x = intersection.getX();
        y = intersection.getY();
        yVelocity *= -1;
      }
    }

    previousX = x;
    previousY = y;
    // change the ball coordinate according to the velocities
    x = x + xVelocity * elapsedTime / 1000 * pixelsPerMeter;
    y = y + yVelocity * elapsedTime / 1000 * pixelsPerMeter;

    // check the collision between the ball and the paddle
    if (collideWith(paddle)) {
      MainGamePanel.playPaddleHitSound();
      resetConsecutiveHits();
      yVelocity *= -1;
      changeBallAngle(paddle, item);
    }

    // check the collision between the ball and the blocks
    if (spiky) {
      spikyCollision(blocks, explosion, map, edges);
      // handleBlocksCollision(blocks, explosion, map, edges);
    }
    else {
      handleBlocksCollision(blocks, explosion, map, edges);
    }
  }

  /**
   * Draw the ball to the canvas.
   * 
   * @param canvas
   */
  public void draw(Canvas canvas) {
    if (!eaten) {
      canvas.drawBitmap(bitmap, x, y, null);
    }
  }

  /**
   * Whether the ball is in the shark.
   * 
   * @param shark
   * @return
   */
  public boolean isInShark(Shark shark) {
    // ball is to the right of the shark
    if (x > shark.getX() + shark.getWidth() && previousX > shark.getX() + shark.getWidth()) {
      return false;
    }
    // ball is to the left of the shark
    else if (x + ballWidth < shark.getX() && previousX + ballWidth < shark.getX()) {
      return false;
    }
    // ball is above the shark
    else if (y + ballHeight < shark.getY() && previousY + ballHeight < shark.getY()) {
      return false;
    }
    // ball is below the shark
    else if (y > shark.getY() + shark.getHeight() && previousY > shark.getY() + shark.getHeight()) {
      return false;
    }
    return true;
  }

  /**
   * Whether the ball is in the fish.
   * 
   * @param fishList
   * @return
   */
  public boolean isInFish(Fish fish) {

    if (x > fish.getX() + fish.getWidth() && previousX > fish.getX() + fish.getWidth()) {
      return false;
    }
    // ball is to the left of the shark
    else if (x + ballWidth < fish.getX() && previousX + ballWidth < fish.getX()) {
      return false;
    }
    // ball is above the shark
    else if (y + ballHeight < fish.getY() && previousY + ballHeight < fish.getY()) {
      return false;
    }
    // ball is below the shark
    else if (y > fish.getY() + fish.getHeight() && previousY > fish.getY() + fish.getHeight()) {
      return false;
    }

    return true;
  }

  /**
   * Let the ball rest on top of the paddle wherever the paddle moves.
   * 
   * @param paddle
   */
  public void followPaddle(Paddle paddle) {
    previousX = x;
    previousY = y;
    this.x = paddle.getX() + paddle.getWidth() / 2 - ballWidth / 2;
    this.y = paddle.getY() - 10;
  }

  /**
   * Whether the ball is in contact with the paddle.
   * 
   * @param paddle
   * @return
   */
  public boolean collideWith(Paddle paddle) {

    if (yVelocity > 0 && y + ballHeight >= paddle.getY()) {
      if (previousY + ballHeight > paddle.getY() + paddle.getHeight()) {
        return false;
      }
      // check bottom left corner of the ball <-> top edge of paddle
      Point point =
          findIntersection(paddle.getSurfaceLine(), previousX, previousY + ballHeight, x, y
              + ballHeight);
      if (point != null) {
        x = point.getX();
        y = paddle.getY() - ballHeight;
        return true;
      }

      // check bottom right corner of the ball <-> top edge of paddle
      point =
          findIntersection(paddle.getSurfaceLine(), previousX + ballWidth, previousY + ballHeight,
              x + ballWidth, y + ballHeight);
      if (point != null) {
        x = point.getX() - ballWidth;
        y = paddle.getY() - ballHeight;
        return true;
      }

      // check top right corner of the ball <-> left of the paddle
      point = findIntersection(paddle.leftEdge, previousX + ballWidth, previousY, x + ballWidth, y);

      if (point != null) {
        x = point.getX() - ballWidth;
        y = point.getY();
        return true;
      }

      // check bottom right corner of the ball <-> left of the paddle
      point =
          findIntersection(paddle.leftEdge, previousX + ballWidth, previousY + ballHeight, x
              + ballWidth, y + ballHeight);

      if (point != null) {
        x = point.getX() - ballWidth;
        y = point.getY() - ballHeight;
        return true;
      }

      // check top left corner of the ball <-> right of the paddle
      point = findIntersection(paddle.rightEdge, previousX, previousY, x, y);

      if (point != null) {
        x = point.getX();
        y = point.getY();
        return true;
      }

      // check bottom left corner of the ball <-> right of the paddle
      point =
          findIntersection(paddle.rightEdge, previousX, previousY + ballHeight, x, y + ballHeight);

      if (point != null) {
        x = point.getX();
        y = point.getY() - ballHeight;
        return true;
      }
    }
    return false;
  }

  /**
   * Find the intersection of two line segments where second line is represented by two points. (cx,
   * cy) and (dx, dy)
   * 
   * @param line
   * @param cx
   * @param cy
   * @param dx
   * @param dy
   * @return
   */
  public Point findIntersection(Line line, float cx, float cy, float dx, float dy) {
    return findIntersection(line.getPoint1().getX(), line.getPoint1().getY(), line.getPoint2()
        .getX(), line.getPoint2().getY(), cx, cy, dx, dy);
  }

  /**
   * Find the intersection of two line segments. If there's no intersection a null is returned. a,
   * b, c, d are points. Line 1 = (a, b) and Line 2 = (c, d) Source:
   * http://goosebumps4all.net/34all/bb/showthread.php?tid=113
   * 
   * @param ax
   * @param ay
   * @param bx
   * @param by
   * @param cx
   * @param cy
   * @param dx
   * @param dy
   * @return
   */
  public Point findIntersection(float ax, float ay, float bx, float by, float cx, float cy,
      float dx, float dy) {
    float distAB;
    float sinValue;
    float cosValue;
    float newX;
    float posAB;

    // return null if either line has length 0
    if ((ax == bx && ay == by) || (cx == dx && cy == dy)) {
      return null;
    }
    // if the end of the lines intersect
    if (ax == cx && ay == cy || bx == cx && by == cy) {
      return new Point(cx, cy);
    }
    if (ax == dx && ay == dy || bx == dx && by == dy) {
      return new Point(dx, dy);
    }

    // (1) Translate the system so that point A is on the origin.
    bx -= ax;
    by -= ay;
    cx -= ax;
    cy -= ay;
    dx -= ax;
    dy -= ay;

    // Discover the length of segment A-B.
    distAB = (float) Math.sqrt(bx * bx + by * by);

    // (2) Rotate the system so that point B is on the positive X axis.
    cosValue = bx / distAB;
    sinValue = by / distAB;
    newX = cx * cosValue + cy * sinValue;
    cy = cy * cosValue - cx * sinValue;
    cx = newX;
    newX = dx * cosValue + dy * sinValue;
    dy = dy * cosValue - dx * sinValue;
    dx = newX;

    // Fail if segment C-D doesn't cross line A-B.
    if ((cy < 0. && dy < 0.) || (cy >= 0. && dy >= 0.)) {
      return null;
    }

    // (3) Discover the position of the intersection point along line A-B.
    posAB = dx + (cx - dx) * dy / (dy - cy);

    // Fail if segment C-D crosses line A-B outside of segment A-B.
    if (posAB < 0. || posAB > distAB) {
      return null;
    }

    // (4) Apply the discovered position to line A-B in the original coordinate system.
    return new Point(ax + posAB * cosValue, ay + posAB * sinValue);
  }

  public boolean hasIntersection(Line line, float cx, float cy, float dx, float dy) {
    return hasIntersection(line.getPoint1().getX(), line.getPoint1().getY(), line.getPoint2()
        .getX(), line.getPoint2().getY(), cx, cy, dx, dy);
  }

  /**
   * Whether there is a intersection between the two lines. Created this method because don't want
   * to create more Point objects when we don't need them. This is easier for the garbage
   * collector..
   * 
   * @param ax
   * @param ay
   * @param bx
   * @param by
   * @param cx
   * @param cy
   * @param dx
   * @param dy
   * @return
   */
  public boolean hasIntersection(float ax, float ay, float bx, float by, float cx, float cy,
      float dx, float dy) {
    float distAB;
    float sinValue;
    float cosValue;
    float newX;
    float posAB;

    // return null if either line has length 0
    if ((ax == bx && ay == by) || (cx == dx && cy == dy)) {
      return false;
    }
    // if the end of the lines intersect
    if (ax == cx && ay == cy || bx == cx && by == cy) {
      return true;
    }
    if (ax == dx && ay == dy || bx == dx && by == dy) {
      return true;
    }

    // (1) Translate the system so that point A is on the origin.
    bx -= ax;
    by -= ay;
    cx -= ax;
    cy -= ay;
    dx -= ax;
    dy -= ay;

    // Discover the length of segment A-B.
    distAB = (float) Math.sqrt(bx * bx + by * by);

    // (2) Rotate the system so that point B is on the positive X axis.
    cosValue = bx / distAB;
    sinValue = by / distAB;
    newX = cx * cosValue + cy * sinValue;
    cy = cy * cosValue - cx * sinValue;
    cx = newX;
    newX = dx * cosValue + dy * sinValue;
    dy = dy * cosValue - dx * sinValue;
    dx = newX;

    // Fail if segment C-D doesn't cross line A-B.
    if ((cy < 0. && dy < 0.) || (cy >= 0. && dy >= 0.)) {
      return false;
    }

    // (3) Discover the position of the intersection point along line A-B.
    posAB = dx + (cx - dx) * dy / (dy - cy);

    // Fail if segment C-D crosses line A-B outside of segment A-B.
    if (posAB < 0. || posAB > distAB) {
      return false;
    }

    // (4) Apply the discovered position to line A-B in the original coordinate system.
    return true;
  }

  /**
   * Change ball angle according to where it hit the paddle. The ball moves more to the left if it
   * hits the left half of the paddle. and vise versa.
   */
  public void changeBallAngle(Paddle paddle, Item item) {
    float change = x - (paddle.getX() + paddle.getWidth() / 2 - ballWidth / 2);
    xVelocity = change / 2;

    if (item.getTimeRemaining() != -1 && item.getType() != null
        && item.getType() == Item.LARGER_PADDLE) {
      xVelocity = xVelocity / 2;
    }
    else if (item.getTimeRemaining() != -1 && item.getType() != null
        && item.getType() == Item.SMALLER_PADDLE) {
      xVelocity = xVelocity * 2;
    }

    if (xVelocity > 14) {
      xVelocity = 14;
    }
    else if (xVelocity < -14) {
      xVelocity = -14;
    }

    adjustYVelocity();

    if (!item.isVisible() && getRandomBoolean() && item.getTimeRemaining() < 0) {
      item.appear();
    }
  }

  /**
   * Handles block collisions of a normal ball.
   * 
   * @param blocks
   * @param explosion
   * @param map
   * @param edges
   */
  public void handleBlocksCollision(List<Block> blocks, Explosion explosion,
      HashMap<Line, Block> map, BlockEdges edges) {

    // no need to check for collision if ball isn't touching the blocks
    if (edges.ballIsAboveHighestEdge(this) || edges.ballIsBelowLowestEdge(this)
        || edges.ballIsLeftToLeftmostEdge(this) || edges.ballIsRightToRightmostEdge(this)) {
      return;
    }

    // Log.d(MainGamePanel.TAG, "checking collision...");

    Point intersection;
    Line collisionEdgeVertical = null;
    Line collisionEdgeHorizontal = null;
    // 0 for xVelocity, 1 for y velocity
    boolean collisionFound = false;
    // compare to find the shortest distance after
    double distance1 = 1000;
    double distance2 = 1000;
    float tempX1 = 0;
    float tempY1 = 0;
    float tempX2 = 0;
    float tempY2 = 0;

    // number of left, right, top, bottom edges should be the same..so using anyone here.
    // check through edges until we find collision
    for (int i = 0; i < edges.getLeftEdges().size(); i++) {

      if (distance1 < 1000 && distance2 < 1000) {
        break;
      }

      intersection = null;

      // ball moving right so only need to check left edges
      if (xVelocity > 0 && collisionEdgeVertical == null && edges.getLeftEdges().size() > 0) {
        boolean topCornerHit;
        // check left edges
        // top right corner of the ball
        topCornerHit = true;
        intersection =
            findIntersection(previousX + ballWidth, previousY, x + ballWidth, y, edges
                .getLeftEdges().get(i).getPoint1().getX(), edges.getLeftEdges().get(i).getPoint1()
                .getY(), edges.getLeftEdges().get(i).getPoint2().getX(), edges.getLeftEdges()
                .get(i).getPoint2().getY());

        // if null check bottom right corner of the ball
        if (intersection == null) {
          topCornerHit = false;
          intersection =
              findIntersection(previousX + ballWidth, previousY + ballHeight, x + ballWidth, y
                  + ballHeight, edges.getLeftEdges().get(i).getPoint1().getX(), edges
                  .getLeftEdges().get(i).getPoint1().getY(), edges.getLeftEdges().get(i)
                  .getPoint2().getX(), edges.getLeftEdges().get(i).getPoint2().getY());
        }

        if (intersection != null) {
          collisionFound = true;
          if (topCornerHit) {
            tempY1 = intersection.getY();
          }
          else {
            tempY1 = intersection.getY() - ballHeight;
          }
          tempX1 = intersection.getX() - ballWidth;
          distance1 =
              Math.sqrt((intersection.getX() - previousX) * (intersection.getX() - previousX)
                  + (intersection.getY() - previousY) * (intersection.getY() - previousY));

          collisionEdgeVertical = edges.getLeftEdges().get(i);
        }
      }
      // check right edges
      if (xVelocity < 0 && collisionEdgeVertical == null && edges.getRightEdges().size() > 0) {
        boolean topCornerHit;
        // top left corner of the ball
        topCornerHit = true;
        intersection =
            findIntersection(previousX, previousY, x, y, edges.getRightEdges().get(i).getPoint1()
                .getX(), edges.getRightEdges().get(i).getPoint1().getY(), edges.getRightEdges()
                .get(i).getPoint2().getX(), edges.getRightEdges().get(i).getPoint2().getY());

        // if null check bottom left corner of the ball
        if (intersection == null) {
          topCornerHit = false;
          intersection =
              findIntersection(previousX, previousY + ballHeight, x, y + ballHeight, edges
                  .getRightEdges().get(i).getPoint1().getX(), edges.getRightEdges().get(i)
                  .getPoint1().getY(), edges.getRightEdges().get(i).getPoint2().getX(), edges
                  .getRightEdges().get(i).getPoint2().getY());
        }

        if (intersection != null) {
          collisionFound = true;
          if (topCornerHit) {
            tempY1 = intersection.getY();
          }
          else {
            tempY1 = intersection.getY() - ballHeight;
          }
          tempX1 = intersection.getX();
          distance1 =
              Math.sqrt((intersection.getX() - previousX) * (intersection.getX() - previousX)
                  + (intersection.getY() - previousY) * (intersection.getY() - previousY));

          collisionEdgeVertical = edges.getRightEdges().get(i);
        }
      }
      // check bottom edges
      if (yVelocity < 0 && collisionEdgeHorizontal == null && edges.getBottomEdges().size() > 0) {
        boolean leftCornerHit;
        // top left corner of the ball
        leftCornerHit = true;
        intersection =
            findIntersection(previousX, previousY, x, y, edges.getBottomEdges().get(i).getPoint1()
                .getX(), edges.getBottomEdges().get(i).getPoint1().getY(), edges.getBottomEdges()
                .get(i).getPoint2().getX(), edges.getBottomEdges().get(i).getPoint2().getY());

        // if null check top right corner of the ball
        if (intersection == null) {
          leftCornerHit = false;
          intersection =
              findIntersection(previousX + ballWidth, previousY, x + ballWidth, y, edges
                  .getBottomEdges().get(i).getPoint1().getX(), edges.getBottomEdges().get(i)
                  .getPoint1().getY(), edges.getBottomEdges().get(i).getPoint2().getX(), edges
                  .getBottomEdges().get(i).getPoint2().getY());
        }

        if (intersection != null) {
          collisionFound = true;
          if (leftCornerHit) {
            tempX2 = intersection.getX();
          }
          else {
            tempX2 = intersection.getX() - ballWidth;
          }
          tempY2 = intersection.getY();
          distance2 =
              Math.sqrt((intersection.getX() - previousX) * (intersection.getX() - previousX)
                  + (intersection.getY() - previousY) * (intersection.getY() - previousY));

          collisionEdgeHorizontal = edges.getBottomEdges().get(i);
        }
      }
      // check top edges
      if (yVelocity > 0 && collisionEdgeHorizontal == null && edges.getTopEdges().size() > 0) {
        boolean leftCornerHit;
        // bottom left corner of the ball
        leftCornerHit = true;
        intersection =
            findIntersection(previousX, previousY + ballHeight, x, y + ballHeight, edges
                .getTopEdges().get(i).getPoint1().getX(), edges.getTopEdges().get(i).getPoint1()
                .getY(), edges.getTopEdges().get(i).getPoint2().getX(), edges.getTopEdges().get(i)
                .getPoint2().getY());

        // if null check bottom right corner of the ball
        if (intersection == null) {
          leftCornerHit = false;
          intersection =
              findIntersection(previousX + ballWidth, previousY + ballHeight, x + ballWidth, y
                  + ballHeight, edges.getTopEdges().get(i).getPoint1().getX(), edges.getTopEdges()
                  .get(i).getPoint1().getY(), edges.getTopEdges().get(i).getPoint2().getX(), edges
                  .getTopEdges().get(i).getPoint2().getY());
        }

        if (intersection != null) {
          collisionFound = true;
          if (leftCornerHit) {
            tempX2 = intersection.getX();
          }
          else {
            tempX2 = intersection.getX() - ballWidth;
          }
          tempY2 = intersection.getY() - ballHeight;
          distance2 =
              Math.sqrt((intersection.getX() - previousX) * (intersection.getX() - previousX)
                  + (intersection.getY() - previousY) * (intersection.getY() - previousY));

          collisionEdgeHorizontal = edges.getTopEdges().get(i);
        }
      }
    }
    if (collisionFound) {
      // reverse x velocity
      if (distance1 < distance2) {
        handleCollisionResult(blocks, collisionEdgeVertical, explosion, map, edges, 0, tempX1,
            tempY1);
      }
      // reverse y velocity
      else if (distance1 > distance2) {
        handleCollisionResult(blocks, collisionEdgeHorizontal, explosion, map, edges, 1, tempX2,
            tempY2);
      }
    }
  }

  /**
   * Reverse ball direction; remove blocks and edges; Handles stuff to do when a block is destroyed.
   * 
   * @param blocks
   * @param line
   * @param explosion
   * @param map
   * @param edges
   * @param velocityType 0 to reverse xVelocity, 1 to reverse yVelocity
   */
  public void handleCollisionResult(List<Block> blocks, Line line, Explosion explosion,
      HashMap<Line, Block> map, BlockEdges edges, int velocityType, float x, float y) {

    if (line == null) {
      return;
    }

    if (map.get(line) == null) {
      return;
    }

    if (map.get(line).getColor() == Block.BLACK) {
      destroyNearbyBlocks(blocks, map.get(line), explosion, edges);

      if (!spiky) {
        this.x = x;
        this.y = y;
        reverseVelocity(velocityType);
      }

      return;
    }
    else if (map.get(line).getColor() == Block.GRAY) {
      if (!this.ghost) {
        this.x = x;
        this.y = y;
        reverseVelocity(velocityType);
      }
      return;
    }
    else {
      MainGamePanel.playBlockHitSound();
      if (!spiky) {
        map.get(line).gotHit();
        if (map.get(line).getHitsLeft() == 0) {
          edges.removeBlockEdges(map.get(line));
          blocks.remove(map.get(line));
        }
        MainThread.addScore(100 + consecutiveHits * 25);
        consecutiveHits++;
      }
      else {
        if (map.get(line).getColor() == Block.GREEN) {
          MainThread.addScore(100 + consecutiveHits * 25);
        }
        else if (map.get(line).getColor() == Block.YELLOW) {
          MainThread.addScore(200 + consecutiveHits * 25);
        }
        else {
          MainThread.addScore(300 + consecutiveHits * 25);
        }
        edges.removeBlockEdges(map.get(line));
        blocks.remove(map.get(line));
        consecutiveHits++;
      }

      if (!spiky) {
        this.x = x;
        this.y = y;
        reverseVelocity(velocityType);
      }
      return;
    }
  }

  /**
   * Pass in 0 to reverse xVelocity and 1 to reverse yVelocity.
   * 
   * @param velocityType
   */
  public void reverseVelocity(int velocityType) {
    if (velocityType == 0) {
      xVelocity *= -1;
    }
    else if (velocityType == 1) {
      yVelocity *= -1;
    }
  }

  /**
   * Handles spiked ball collision.
   * 
   * @param blocks
   * @param explosion
   * @param edges
   */
  public void spikyCollision(List<Block> blocks, Explosion explosion, HashMap<Line, Block> map,
      BlockEdges edges) {

    // no need to check for collision if ball isn't touching the blocks
    if (edges.ballIsAboveHighestEdge(this) || edges.ballIsBelowLowestEdge(this)
        || edges.ballIsLeftToLeftmostEdge(this) || edges.ballIsRightToRightmostEdge(this)) {
      return;
    }

    Point intersection;

    // variables to store the edges that came in contact with the ball.
    Line collisionRightEdge;
    Line collisionLeftEdge;
    Line collisionTopEdge;
    Line collisionBottomEdge;

    // variables to know which corner(s) touched the block.
    boolean topRight;
    boolean topLeft;
    boolean bottomRight;
    boolean bottomLeft;

    // need these to know whether the ball is closer to the vertical or horizontal edge
    // so we know which velocity to reverse
    double distanceToVertical = 1000;
    double distanceToHorizontal = 1000;

    // variables to store the positions of different intersections of different edges
    float leftX = 0;
    float leftY = 0;
    float rightX = 0;
    float rightY = 0;
    float topX = 0;
    float topY = 0;
    float bottomX = 0;
    float bottomY = 0;

    // size of left edges is equal to the number of blocks left..can use any type of edge here.
    for (int i = 0; i < edges.getLeftEdges().size(); i++) {

      intersection = null;
      collisionRightEdge = null;
      collisionLeftEdge = null;
      collisionTopEdge = null;
      collisionBottomEdge = null;

      topRight = false;
      topLeft = false;
      bottomRight = false;
      bottomLeft = false;

      distanceToVertical = 1000;
      distanceToHorizontal = 1000;

      // check left edges
      if (xVelocity > 0) {
        // top right corner of the ball
        intersection =
            findIntersection(previousX + ballWidth, previousY, x + ballWidth, y, edges
                .getLeftEdges().get(i).getPoint1().getX(), edges.getLeftEdges().get(i).getPoint1()
                .getY(), edges.getLeftEdges().get(i).getPoint2().getX(), edges.getLeftEdges()
                .get(i).getPoint2().getY());

        if (intersection != null) {
          topRight = true;
          collisionLeftEdge = edges.getLeftEdges().get(i);
          distanceToVertical =
              Math.sqrt((intersection.getX() - previousX) * (intersection.getX() - previousX)
                  + (intersection.getY() - previousY) * (intersection.getY() - previousY));
          leftX = intersection.getX() - ballWidth;
          leftY = intersection.getY();
        }

        // bottom right corner of the ball
        intersection =
            findIntersection(previousX + ballWidth, previousY + ballHeight, x + ballWidth, y
                + ballHeight, edges.getLeftEdges().get(i).getPoint1().getX(), edges.getLeftEdges()
                .get(i).getPoint1().getY(), edges.getLeftEdges().get(i).getPoint2().getX(), edges
                .getLeftEdges().get(i).getPoint2().getY());

        if (intersection != null) {
          bottomRight = true;
          collisionLeftEdge = edges.getLeftEdges().get(i);
          distanceToVertical =
              Math.sqrt((intersection.getX() - previousX) * (intersection.getX() - previousX)
                  + (intersection.getY() - previousY) * (intersection.getY() - previousY));
          leftX = intersection.getX() - ballWidth;
          leftY = intersection.getY() - ballHeight;
        }
      }

      // check right edges
      if (xVelocity < 0) {
        // top left corner of the ball
        intersection =
            findIntersection(previousX, previousY, x, y, edges.getRightEdges().get(i).getPoint1()
                .getX(), edges.getRightEdges().get(i).getPoint1().getY(), edges.getRightEdges()
                .get(i).getPoint2().getX(), edges.getRightEdges().get(i).getPoint2().getY());

        if (intersection != null) {
          topLeft = true;
          collisionRightEdge = edges.getRightEdges().get(i);
          distanceToVertical =
              Math.sqrt((intersection.getX() - previousX) * (intersection.getX() - previousX)
                  + (intersection.getY() - previousY) * (intersection.getY() - previousY));
          rightX = intersection.getX();
          rightY = intersection.getY();
        }

        // bottom left corner of the ball
        intersection =
            findIntersection(previousX, previousY + ballHeight, x, y + ballHeight, edges
                .getRightEdges().get(i).getPoint1().getX(), edges.getRightEdges().get(i)
                .getPoint1().getY(), edges.getRightEdges().get(i).getPoint2().getX(), edges
                .getRightEdges().get(i).getPoint2().getY());

        if (intersection != null) {
          bottomLeft = true;
          collisionRightEdge = edges.getRightEdges().get(i);
          distanceToVertical =
              Math.sqrt((intersection.getX() - previousX) * (intersection.getX() - previousX)
                  + (intersection.getY() - previousY) * (intersection.getY() - previousY));
          rightX = intersection.getX();
          rightY = intersection.getY() - ballHeight;
        }
      }

      // check bottom edges
      if (yVelocity < 0) {
        // top left corner of the ball
        intersection =
            findIntersection(previousX, previousY, x, y, edges.getBottomEdges().get(i).getPoint1()
                .getX(), edges.getBottomEdges().get(i).getPoint1().getY(), edges.getBottomEdges()
                .get(i).getPoint2().getX(), edges.getBottomEdges().get(i).getPoint2().getY());

        if (intersection != null) {
          topLeft = true;
          collisionBottomEdge = edges.getBottomEdges().get(i);
          distanceToHorizontal =
              Math.sqrt((intersection.getX() - previousX) * (intersection.getX() - previousX)
                  + (intersection.getY() - previousY) * (intersection.getY() - previousY));
          bottomX = intersection.getX();
          bottomY = intersection.getY();
        }

        // top right corner of the ball
        intersection =
            findIntersection(previousX + ballWidth, previousY, x + ballWidth, y, edges
                .getBottomEdges().get(i).getPoint1().getX(), edges.getBottomEdges().get(i)
                .getPoint1().getY(), edges.getBottomEdges().get(i).getPoint2().getX(), edges
                .getBottomEdges().get(i).getPoint2().getY());

        if (intersection != null) {
          topRight = true;
          collisionBottomEdge = edges.getBottomEdges().get(i);
          distanceToHorizontal =
              Math.sqrt((intersection.getX() - previousX) * (intersection.getX() - previousX)
                  + (intersection.getY() - previousY) * (intersection.getY() - previousY));
          bottomX = intersection.getX() - ballWidth;
          bottomY = intersection.getY();
        }
      }

      // check top edges
      if (yVelocity > 0) {
        // bottom left corner of the ball
        intersection =
            findIntersection(previousX, previousY + ballHeight, x, y + ballHeight, edges
                .getTopEdges().get(i).getPoint1().getX(), edges.getTopEdges().get(i).getPoint1()
                .getY(), edges.getTopEdges().get(i).getPoint2().getX(), edges.getTopEdges().get(i)
                .getPoint2().getY());

        if (intersection != null) {
          bottomLeft = true;
          collisionTopEdge = edges.getTopEdges().get(i);
          distanceToHorizontal =
              Math.sqrt((intersection.getX() - previousX) * (intersection.getX() - previousX)
                  + (intersection.getY() - previousY) * (intersection.getY() - previousY));
          topX = intersection.getX();
          topY = intersection.getY() - ballHeight;
        }

        // bottom right corner of the ball
        intersection =
            findIntersection(previousX + ballWidth, previousY + ballHeight, x + ballWidth, y
                + ballHeight, edges.getTopEdges().get(i).getPoint1().getX(), edges.getTopEdges()
                .get(i).getPoint1().getY(), edges.getTopEdges().get(i).getPoint2().getX(), edges
                .getTopEdges().get(i).getPoint2().getY());

        if (intersection != null) {
          bottomRight = true;
          collisionTopEdge = edges.getTopEdges().get(i);
          distanceToHorizontal =
              Math.sqrt((intersection.getX() - previousX) * (intersection.getX() - previousX)
                  + (intersection.getY() - previousY) * (intersection.getY() - previousY));
          topX = intersection.getX() - ballWidth;
          topY = intersection.getY() - ballHeight;
        }
      }

      // reverse xVelocity
      if (distanceToVertical < distanceToHorizontal) {
        // if the block is gray we need to pass in the coordinate of the intersection
        // between the ball and the block
        if (collisionLeftEdge != null && map.get(collisionLeftEdge).getColor() == Block.GRAY) {
          handleCollisionResult(blocks, collisionLeftEdge, explosion, map, edges, 0, leftX, leftY);
          return;
        }
        else if (collisionRightEdge != null && map.get(collisionRightEdge).getColor() == Block.GRAY) {
          handleCollisionResult(blocks, collisionRightEdge, explosion, map, edges, 0, rightX,
              rightY);
          return;
        }
      }
      // reverse yVelocity
      else if (distanceToVertical > distanceToHorizontal) {
        // if the block is gray we need to pass in the coordinate of the intersection
        // between the ball and the block
        if (collisionTopEdge != null && map.get(collisionTopEdge).getColor() == Block.GRAY) {
          handleCollisionResult(blocks, collisionTopEdge, explosion, map, edges, 1, topX, topY);
          return;
        }
        else if (collisionBottomEdge != null
            && map.get(collisionBottomEdge).getColor() == Block.GRAY) {
          handleCollisionResult(blocks, collisionBottomEdge, explosion, map, edges, 1, bottomX,
              bottomY);
          return;
        }
      }
      // special case: ball corner touching block corner
      else if (distanceToVertical == distanceToHorizontal && distanceToVertical < 1000) {
        if (topLeft && topRight) {
          handleCollisionResult(blocks, collisionBottomEdge, explosion, map, edges, 1, bottomX,
              bottomY);
        }
        if (bottomLeft && bottomRight) {
          handleCollisionResult(blocks, collisionTopEdge, explosion, map, edges, 1, topX, topY);
        }
        if (topLeft && bottomLeft) {
          handleCollisionResult(blocks, collisionRightEdge, explosion, map, edges, 0, rightX,
              rightY);
        }
        if (topRight && bottomRight) {
          handleCollisionResult(blocks, collisionLeftEdge, explosion, map, edges, 0, leftX, leftY);
        }
      }

      // if a block will be removed at the end of this iteration, we decrement i by 1 because all
      // edges will be shifted up after the block at index i is gone.
      if (collisionTopEdge != null || collisionBottomEdge != null || collisionLeftEdge != null
          || collisionRightEdge != null) {
        i--;
      }

      // finally, if no gray block was detected, remove everything the block came in contact with.
      handleCollisionResult(blocks, collisionTopEdge, explosion, map, edges, 1, 0, 0);
      handleCollisionResult(blocks, collisionBottomEdge, explosion, map, edges, 1, 0, 0);
      handleCollisionResult(blocks, collisionLeftEdge, explosion, map, edges, 0, 0, 0);
      handleCollisionResult(blocks, collisionRightEdge, explosion, map, edges, 0, 0, 0);
    }
  }

  /**
   * Random boolean generator for item appearance.
   * 
   * @return
   */
  public boolean getRandomBoolean() {
    // 100% now just for testing purpose
    return (int) (Math.random() * 100) <= 100;
  }

  /**
   * Return the last update time.
   * 
   * @return The last update time.
   */
  public long getLastUpdateTime() {
    return lastUpdateTime;
  }

  /**
   * Set the last update time.
   * 
   * @param lastUpdateTime
   */
  public void setLastUpdateTime(long lastUpdateTime) {
    this.lastUpdateTime = lastUpdateTime;
  }

  /**
   * Remove the nearby destructible blocks of the bomb passed in.
   * 
   * @param blocks
   */
  public void destroyNearbyBlocks(List<Block> blocks, Block bomb, Explosion explosion,
      BlockEdges edges) {

    MainGamePanel.playExplosionSound();

    int horizontalDistance = bomb.getWidth() + MainGamePanel.PIXELS_BETWEEN_BLOCKS;
    int verticalDistance = bomb.getHeight() + MainGamePanel.PIXELS_BETWEEN_BLOCKS;

    explosion.startExplosion(bomb);
    edges.removeBlockEdges(bomb);
    blocks.remove(bomb);
    consecutiveHits++;

    Block block;
    Iterator<Block> iterator = blocks.iterator();
    while (iterator.hasNext()) {

      block = iterator.next();

      // everything within this bomb's 1 row above and 1 row below
      if (Math.abs(bomb.getY() - block.getY()) <= verticalDistance) {
        // everything within this bomb's 1 column left and 1 column right
        if (Math.abs(bomb.getX() - block.getX()) <= horizontalDistance) {

          if (block.getColor() != Block.GRAY) {
            edges.removeBlockEdges(block);
            iterator.remove();
            if (block.getColor() == Block.RED) {
              MainThread.addScore(300 + (25 * consecutiveHits));
            }
            if (block.getColor() == Block.YELLOW) {
              MainThread.addScore(200 + (25 * consecutiveHits));
            }
            if (block.getColor() == Block.GREEN) {
              MainThread.addScore(100 + (25 * consecutiveHits));
            }

            /*******************************************************
             * if color is black have to call this method recursively skipping this part for now
             * since in our design there will be no bombs beside each other. and the code below
             * doesn't work so i commented it out
             *******************************************************/
            // call this function recursively for chain reaction
            // if (block.getColor() == Block.BLACK) {
            // destroyNearbyBlocks(blocks, block);
            // }

            // just doing the same thing as green block for now
            if (block.getColor() == Block.BLACK) {
              MainThread.addScore(100 + (25 * consecutiveHits));
            }
            consecutiveHits++;
          }
        }
      }
    }
  }
}
